home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1996 May: Tool Chest / Developer CD Series May 1996 (Tool Chest) (Apple Computer) (1996).iso / Tool Chest / Development Tools & Languages / Dylan Related / Mindy / Mindy 1.2 - portable sources / libraries / print / pprint.dylan < prev    next >
Encoding:
Text File  |  1995-03-15  |  39.2 KB  |  1,197 lines  |  [TEXT/ttxt]

  1. module: pprint
  2. author: wlott@cs.cmu.edu
  3. synopsis: Most of Dick Water's pretty printer.
  4. copyright: See below.
  5. rcs-header: $Header: pprint.dylan,v 1.5 94/11/28 12:05:35 wlott Exp $
  6.  
  7. //======================================================================
  8. //
  9. // Copyright (c) 1994  Carnegie Mellon University
  10. // All rights reserved.
  11. // 
  12. // Use and copying of this software and preparation of derivative
  13. // works based on this software are permitted, including commercial
  14. // use, provided that the following conditions are observed:
  15. // 
  16. // 1. This copyright notice must be retained in full on any copies
  17. //    and on appropriate parts of any derivative works.
  18. // 2. Documentation (paper or online) accompanying any system that
  19. //    incorporates this software, or any part of it, must acknowledge
  20. //    the contribution of the Gwydion Project at Carnegie Mellon
  21. //    University.
  22. // 
  23. // This software is made available "as is".  Neither the authors nor
  24. // Carnegie Mellon University make any warranty about the software,
  25. // its performance, or its conformity to any specification.
  26. // 
  27. // Bug reports, questions, comments, and suggestions should be sent by
  28. // E-mail to the Internet address "gwydion-bugs@cs.cmu.edu".
  29. //
  30. //======================================================================
  31. //
  32.  
  33. /// This file contains a more or less straight conversion of CMU Common Lisp's
  34. /// rewrite of the Dick Water's pretty printer code.
  35. ///
  36.  
  37.  
  38.  
  39. //// User tunable parameters.
  40.  
  41. define variable *print-miser-width* :: false-or(<fixed-integer>) = #f;
  42.  
  43. define variable *default-line-length* :: <fixed-integer> = 80;
  44.  
  45.  
  46. //// Random internal constants. 
  47.  
  48. define constant $initial-buffer-size = 128;
  49.  
  50. define constant $newline = as(<fixed-integer>, '\n');
  51.  
  52.  
  53. //// Types.
  54.  
  55. // There are three different units for measuring character positions:
  56. //  <index> - index into the output buffer.
  57. //  <column> - offset (in characters) from the start of the current line.
  58. //  <position> - some position in the stream of characters cycling through
  59. //               the output buffer.
  60. // 
  61. define constant <index> = limited(<fixed-integer>, min: 0);
  62. define constant <column> = limited(<fixed-integer>, min: 0);
  63. define constant <position> = <fixed-integer>;
  64.  
  65. // <pretty-stream> -- exported.
  66. //
  67. // Stream used for pretty printing.
  68. // 
  69. define class <pretty-stream> (<stream>)
  70.   //
  71.   // The stream where the output is finally going to go.
  72.   slot pretty-stream-target :: <stream>, required-init-keyword: target:;
  73.   //
  74.   // The line length for this stream.
  75.   slot pretty-stream-line-length :: <column>,
  76.     init-value: *default-line-length*,
  77.     init-keyword: line-length:;
  78.   //
  79.   // Buffer handed off to the user as part of the stream extension protocol.
  80.   slot pretty-stream-user-buffer :: <buffer>,
  81.     init-function: curry(make, <buffer>);
  82.   //
  83.   // Buffer holding pending output.
  84.   slot pretty-stream-buffer :: <byte-string>,
  85.     init-function: curry(make, <byte-string>, size: $initial-buffer-size);
  86.   //
  87.   // The index into the buffer where more text should be put.
  88.   slot pretty-stream-buffer-fill-pointer :: <index>, init-value: 0;
  89.   //
  90.   // Whenever we output stuff from the buffer, we shift the remaining noise
  91.   // over.  This makes it difficult to keep references to locations in
  92.   // the buffer.  Therefore, we have to keep track of the total amount of
  93.   // stuff that has been shifted out of the buffer.  This is the delta between
  94.   // the <position> and <index> types.
  95.   slot pretty-stream-buffer-offset :: <position>, init-value: 0;
  96.   //
  97.   // The column the first character in the buffer will appear in.  Normally
  98.   // zero, but if we end up with a very long line with no breaks in it we
  99.   // might have to output part of it.  Then this will no longer be zero.
  100.   slot pretty-stream-buffer-start-column :: <column>,
  101.     init-value: 0, init-keyword: column:;
  102.   //
  103.   // The line number we are currently on.  Used for *print-lines* abrevs and
  104.   // to tell when sections have been split across multiple lines.
  105.   slot pretty-stream-line-number :: <fixed-integer>, init-value: 0;
  106.   //
  107.   // Stack of logical blocks in effect at the buffer start.
  108.   slot pretty-stream-blocks :: <list>,
  109.     init-function: compose(list,
  110.                curry(make, <logical-block>,
  111.                  start-column: 0,
  112.                  section-column: 0,
  113.                  per-line-prefix-end: 0,
  114.                  prefix-length: 0,
  115.                  suffix-length: 0,
  116.                  section-start-line: 0));
  117.   //
  118.   // Buffer holding the per-line prefix active at the buffer start.
  119.   // Indentation is included in this.  The amount of this currently in use
  120.   // is stored in the logical block stack.
  121.   slot pretty-stream-prefix :: <byte-string>,
  122.     init-function: curry(make, <byte-string>, size: $initial-buffer-size);
  123.   //
  124.   // Buffer holding the total remaining suffix active at the buffer start.
  125.   // The characters are right-justified in the buffer to make it easier
  126.   // to output the buffer.  The length is stored in the logical block
  127.   // stack.
  128.   slot pretty-stream-suffix :: <byte-string>,
  129.     init-function: curry(make, <byte-string>, size: $initial-buffer-size);
  130.   //
  131.   // Deque of pending operations (indents, newlines, tabs, etc.).  Entries
  132.   // are push-last'ed onto the end, and pop'ed from the front.
  133.   slot pretty-stream-queue :: <deque>,
  134.     init-function: curry(make, <deque>);
  135.   //
  136.   // Stack of block-start queue entries in effect at the queue head.
  137.   slot pretty-stream-pending-blocks :: <list>, init-value: #();
  138.   //
  139.   // Set to #t when the stream is closed.  Can't do anything with it after
  140.   // that.
  141.   slot pretty-stream-closed? :: <boolean>, init-value: #f;
  142. end;
  143.  
  144.  
  145. //// position/column/index conversion routines
  146.  
  147. // index-posn -- internal
  148. //
  149. // Convert from a buffer-index to a position.  Just add the buffer-offset.
  150. //
  151. define method index-posn (index :: <index>, stream :: <pretty-stream>)
  152.     => posn :: <position>;
  153.   index + stream.pretty-stream-buffer-offset;
  154. end;
  155.  
  156. // posn-index -- internal.
  157. //
  158. // Convert from a position to a buffer-index.  Just subtract the buffer-offset.
  159.  
  160. define method posn-index (posn :: <position>, stream :: <pretty-stream>)
  161.     => index :: <index>;
  162.   posn - stream.pretty-stream-buffer-offset;
  163. end;
  164.  
  165. // posn-column -- internal.
  166. //
  167. // Convert a position to a column.  First, convert the position to an index
  168. // and then convert that index to a column.  (Index-column is defined with
  169. // the tab related functions, because it has to take tabs into account.)
  170. // 
  171. define method posn-column (posn :: <position>, stream :: <pretty-stream>)
  172.     => column :: <column>;
  173.   index-column(posn-index(posn, stream), stream);
  174. end;
  175.  
  176.  
  177. //// Stream extension routines.
  178.  
  179. define method stream-extension-get-output-buffer (stream :: <pretty-stream>)
  180.     => (buf :: <buffer>, next :: <buffer-index>, size :: <buffer-index>);
  181.   if (stream.pretty-stream-closed?)
  182.     error("%= has been closed");
  183.   end;
  184.   let buf = stream.pretty-stream-user-buffer;
  185.   values(buf, 0, buf.size);
  186. end;
  187.  
  188. define method stream-extension-release-output-buffer
  189.     (stream :: <pretty-stream>, next :: <buffer-index>)
  190.     => ();
  191.   if (stream.pretty-stream-closed?)
  192.     error("%= has been closed");
  193.   end;
  194.   append-output(stream, stream.pretty-stream-user-buffer, 0, next);
  195. end;
  196.  
  197. define method stream-extension-empty-output-buffer (stream :: <pretty-stream>,
  198.                             stop :: <buffer-index>)
  199.     => ();
  200.   if (stream.pretty-stream-closed?)
  201.     error("%= has been closed");
  202.   end;
  203.   append-output(stream, stream.pretty-stream-user-buffer, 0, stop);
  204. end;
  205.  
  206. define method stream-extension-force-secondary-buffers
  207.     (stream :: <pretty-stream>)
  208.     => ();
  209.   if (stream.pretty-stream-closed?)
  210.     error("%= has been closed");
  211.   end;
  212.   maybe-output(stream, #f);
  213.   unless (zero?(stream.pretty-stream-buffer-fill-pointer))
  214.     let queue = stream.pretty-stream-queue;
  215.     if (empty?(queue) | queue.first.op-posn > 0)
  216.       output-partial-line(stream);
  217.     end;
  218.   end;
  219.   force-output(stream.pretty-stream-target);
  220. end;  
  221.  
  222. define method stream-extension-synchronize (stream :: <pretty-stream>)
  223.     => ();
  224.   if (stream.pretty-stream-closed?)
  225.     error("%= has been closed");
  226.   end;
  227.   maybe-output(stream, #f);
  228.   unless (zero?(stream.pretty-stream-buffer-fill-pointer))
  229.     let queue = stream.pretty-stream-queue;
  230.     if (empty?(queue) | queue.first.op-posn > 0)
  231.       output-partial-line(stream);
  232.     end;
  233.   end;
  234.   synchronize-output(stream.pretty-stream-target);
  235. end;
  236.  
  237. define method close (stream :: <pretty-stream>) => ();
  238.   unless (stream.pretty-stream-closed?)
  239.     maybe-output(stream, #f);
  240.     expand-tabs(stream, #f);
  241.     write(stream.pretty-stream-buffer, stream.pretty-stream-target,
  242.       start: 0, end: stream.pretty-stream-buffer-fill-pointer);
  243.     stream.pretty-stream-closed? := #t;
  244.   end;
  245. end;
  246.  
  247.  
  248. //// Stuff to append output.
  249.  
  250. // append-output -- internal.
  251. //
  252. // Copy a bunch of output into the buffer.  If there are any newlines, they
  253. // get enqueued as ``literal'' conditional newlines.  Everything else is
  254. // just handed to append-raw-output.
  255. // 
  256. define method append-output (stream :: <pretty-stream>, buffer :: <buffer>,
  257.                  start :: <buffer-index>, stop :: <buffer-index>)
  258.     => ();
  259.   local
  260.     method repeat (chunk-start, index)
  261.       if (index == stop)
  262.     unless (chunk-start == index)
  263.       append-raw-output(stream, buffer, chunk-start, index);
  264.     end;
  265.       elseif (buffer[index] == $newline)
  266.     unless (chunk-start == index)
  267.       append-raw-output(stream, buffer, chunk-start, index);
  268.     end;
  269.     enqueue-newline(stream, #"literal");
  270.     repeat(index + 1, index + 1);
  271.       else
  272.     repeat(chunk-start, index + 1);
  273.       end;
  274.     end;
  275.   repeat(start, start);
  276. end;
  277.  
  278. // append-raw-output -- internal.
  279. //
  280. // Actually copy the stuff into the buffer.  Bad things will happen if there
  281. // are any newlines in stuff.
  282. //
  283. // Assure-space-in-buffer is not guarenteed to return all the space we want
  284. // so we might have to iterate.
  285. // 
  286. define method append-raw-output (stream :: <pretty-stream>,
  287.                  stuff :: union(<buffer>, <byte-string>),
  288.                  start :: <buffer-index>,
  289.                  stop :: <buffer-index>)
  290.   let chars = stop - start;
  291.   let available = assure-space-in-buffer(stream, chars);
  292.   let count = min(chars, available);
  293.   let fill-pointer = stream.pretty-stream-buffer-fill-pointer;
  294.   let new-fill-ptr = fill-pointer + count;
  295.   copy-bytes(stream.pretty-stream-buffer, fill-pointer, stuff, start, count);
  296.   stream.pretty-stream-buffer-fill-pointer := new-fill-ptr;
  297.   unless (count == chars)
  298.     append-raw-output(stream, stuff, start + count, stop);
  299.   end;
  300. end;
  301.  
  302.  
  303. //// Logical blocks.
  304.  
  305. // <logical-block> -- internal.
  306. //
  307. // Object representing logical blocks.  Hence the name.  Okay, so this isn't
  308. // a very useful comment, but what else is there to say?
  309. //
  310. define class <logical-block> (<object>)
  311.   //
  312.   // The column this logical block started in.
  313.   slot logical-block-start-column :: <column>,
  314.     required-init-keyword: start-column:;
  315.   //
  316.   // The column the current section started in.
  317.   slot logical-block-section-column :: <column>,
  318.     required-init-keyword: section-column:;
  319.   //
  320.   // The length of the per-line prefix.  We can't move the indentation
  321.   // left of this.
  322.   slot logical-block-per-line-prefix-end :: <index>,
  323.     required-init-keyword: per-line-prefix-end:;
  324.   //
  325.   // The overall length of the prefix, including any indentation.
  326.   slot logical-block-prefix-length :: <index>,
  327.     required-init-keyword: prefix-length:;
  328.   //
  329.   // The overall length of the suffix.
  330.   slot logical-block-suffix-length :: <index>,
  331.     required-init-keyword: suffix-length:;
  332.   // 
  333.   // The line number the current section started on.
  334.   slot logical-block-section-start-line :: <fixed-integer>,
  335.     required-init-keyword: section-start-line:;
  336. end;
  337.  
  338. // really-start-logical-block -- internal.
  339. //
  340. // Called by maybe-output when a logical-block will not fit entirly on one
  341. // line.  We set the indentation to whatever column we are currently at, and
  342. // add the prefix (which is a per-line-prefix) and suffix to the total
  343. // per-line-prefix and suffix stored in the stream.
  344. // 
  345. define method really-start-logical-block (stream :: <pretty-stream>,
  346.                       column :: <column>,
  347.                       prefix :: false-or(<string>),
  348.                       suffix :: false-or(<string>))
  349.   let blocks = stream.pretty-stream-blocks;
  350.   let prev-block = blocks.head;
  351.   let per-line-end = prev-block.logical-block-per-line-prefix-end;
  352.   let prefix-length = prev-block.logical-block-prefix-length;
  353.   let suffix-length = prev-block.logical-block-suffix-length;
  354.   let new-block = make(<logical-block>,
  355.                start-column: column,
  356.                section-column: column,
  357.                per-line-prefix-end: per-line-end,
  358.                prefix-length: prefix-length,
  359.                suffix-length: suffix-length,
  360.                section-start-line: stream.pretty-stream-line-number);
  361.   stream.pretty-stream-blocks := pair(new-block, blocks);
  362.   set-indentation(stream, column);
  363.   if (prefix)
  364.     // We know that we don't have to grow the prefix because set-indentation
  365.     // did it for us.  This is because the prefix has already been output once,
  366.     // resulting in the current column being at the end of the prefix.
  367.     // Therefore, set-indentation grew the prefix enough to put spaces in where
  368.     // we are about to put the per-line-prefix.
  369.     new-block.logical-block-per-line-prefix-end := column;
  370.     copy-bytes(stream.pretty-stream-prefix, column - prefix.size,
  371.            prefix, 0,
  372.            prefix.size);
  373.   end;
  374.   if (suffix)
  375.     let total-suffix = stream.pretty-stream-suffix;
  376.     let total-suffix-len = total-suffix.size;
  377.     let additional = suffix.size;
  378.     let new-suffix-len = suffix-length + additional;
  379.     if (new-suffix-len > total-suffix-len)
  380.       let new-total-suffix-len
  381.     = max(total-suffix-len * 2,
  382.           suffix-length + floor/(additional * 5, 4));
  383.       let new-total-suffix = make(<byte-string>, size: new-total-suffix-len);
  384.       copy-bytes(new-total-suffix, new-total-suffix-len - suffix-length,
  385.          total-suffix, total-suffix-len - suffix-length,
  386.          suffix-length);
  387.       total-suffix := new-total-suffix;
  388.       total-suffix-len := new-total-suffix-len;
  389.       stream.pretty-stream-suffix := total-suffix;
  390.     end;
  391.     copy-bytes(total-suffix, total-suffix-len - new-suffix-len,
  392.            suffix, 0,
  393.            additional);
  394.     new-block.logical-block-suffix-length := new-suffix-len;
  395.   end;
  396. end method;
  397.  
  398. // set-indentation -- internal.
  399. //
  400. // Set the indentation to the given column.  Basically, we just grow the
  401. // per-line-prefix if necessary, and fill the new part with spaces.
  402. //
  403. define method set-indentation (stream :: <pretty-stream>,
  404.                    column :: <column>)
  405.   let prefix = stream.pretty-stream-prefix;
  406.   let prefix-len = prefix.size;
  407.   let this-block = stream.pretty-stream-blocks.head;
  408.   let current = this-block.logical-block-prefix-length;
  409.   let minimum = this-block.logical-block-per-line-prefix-end;
  410.   let column = max(minimum, column);
  411.   if (column > prefix-len)
  412.     let new-prefix-len
  413.       = max(prefix-len * 2, prefix-len + floor/((column - prefix-len) * 5, 4));
  414.     let new-prefix = make(<byte-string>, size: new-prefix-len);
  415.     copy-bytes(new-prefix, 0, prefix, 0, current);
  416.     stream.pretty-stream-prefix := new-prefix;
  417.   end;
  418.   if (column > current)
  419.     fill!(prefix, ' ', start: current, end: column);
  420.   end;
  421.   this-block.logical-block-prefix-length := column;
  422. end;
  423.  
  424. // really-end-logical-block -- internal.
  425. //
  426. // Called by maybe-output at the end of a logical block that didn't fit on
  427. // one line.  We just finish off the block, and reset the indentation.
  428. // 
  429. define method really-end-logical-block (stream :: <pretty-stream>)
  430.   let old = stream.pretty-stream-blocks.head;
  431.   let old-indent = old.logical-block-prefix-length;
  432.   stream.pretty-stream-blocks := stream.pretty-stream-blocks.tail;
  433.   let new = stream.pretty-stream-blocks.head;
  434.   let new-indent = new.logical-block-prefix-length;
  435.   if (new-indent > old-indent)
  436.     fill!(stream.pretty-stream-prefix, ' ',
  437.       start: old-indent, end: new-indent);
  438.   end;
  439. end;
  440.  
  441.  
  442. //// The pending operation queue.
  443.  
  444. // <queued-op> -- internal.
  445. //
  446. // All the different ops that we queue up inherit from this.
  447. //
  448. // We need to queue these things up, because we find out about them as the
  449. // text is being generated (i.e. as stuff is being added to the buffer), but
  450. // we don't act on them until we are actually sending the output on to the
  451. // target (i.e. as stuff is being removed from the buffer).
  452. //
  453. // While stuff is in the buffer/queue, it represents stuff we've been told
  454. // about but haven't decided what to do about yet.
  455. // 
  456. define abstract class <queued-op> (<object>)
  457.   //
  458.   // The position this op occured at.
  459.   slot op-posn :: <position>;
  460. end;
  461.  
  462. // enqueue -- internal.
  463. //
  464. // Add the op to the stream's queue after setting the ops position to
  465. // the current position.
  466. // 
  467. define method enqueue (stream :: <pretty-stream>, op :: <queued-op>)
  468.   op.op-posn := index-posn(stream.pretty-stream-buffer-fill-pointer, stream);
  469.   push-last(stream.pretty-stream-queue, op);
  470.   op;
  471. end;
  472.  
  473. // <section-start> -- internal.
  474. //
  475. // The start of a section.
  476. //
  477. define abstract class <section-start> (<queued-op>)
  478.   //
  479.   // The depth of this section.  I.e. the number of logical blocks
  480.   // surrounding it.
  481.   slot section-start-depth :: <fixed-integer>, required-init-keyword: depth:;
  482.   //
  483.   // The thing that ends this section, or #f if we don't know yet.
  484.   slot section-start-section-end :: type-or(<false>, <newline>, <block-end>),
  485.     init-value: #f;
  486. end;
  487.  
  488. // <newline-kinds> -- internal.
  489. //
  490. // The different kinds of newlines.
  491. // 
  492. define constant <newline-kinds>
  493.   = one-of(#"linear", #"fill", #"miser", #"literal", #"mandatory");
  494.  
  495. // <newline> -- internal.
  496. // 
  497. define class <newline> (<section-start>)
  498.   //
  499.   // The kind of newline it is.
  500.   slot newline-kind :: <newline-kinds>, required-init-keyword: kind:;
  501. end;
  502.  
  503. // enqueue-newline -- internal.
  504. //
  505. // Queue up a newline.  In addition to adding the new newline op to the
  506. // queue, we need to figure out if this newline closes off some section.
  507. // And finally, we check to see if there is any outputting we can do.
  508. //
  509. define method enqueue-newline (stream :: <pretty-stream>,
  510.                    kind :: <newline-kinds>)
  511.   let depth = stream.pretty-stream-pending-blocks.size;
  512.   let newline = enqueue(stream, make(<newline>, kind: kind, depth: depth));
  513.   for (entry in stream.pretty-stream-queue)
  514.     if (~(newline == entry)
  515.       & instance?(entry, <section-start>)
  516.       & ~entry.section-start-section-end
  517.       & depth <= entry.section-start-depth)
  518.       entry.section-start-section-end := newline;
  519.     end;
  520.   end;
  521.   maybe-output(stream, kind == #"literal" | kind == #"mandatory");
  522. end;
  523.  
  524. // <indentation> -- internal.
  525. //
  526. // Represents a change in the indentation.
  527. // 
  528. define class <indentation> (<queued-op>)
  529.   //
  530.   // What the indentation is relative to.
  531.   slot indentation-kind :: one-of(#"block", #"current"),
  532.     required-init-keyword: kind:;
  533.   //
  534.   // The delta.
  535.   slot indentation-amount :: <fixed-integer>,
  536.     required-init-keyword: amount:;
  537. end;
  538.  
  539. // enqueue-indent -- internal.
  540. //
  541. // Queue up a change in the indentation.
  542. // 
  543. define method enqueue-indent (stream :: <pretty-stream>,
  544.                   kind :: one-of(#"block", #"current"),
  545.                   amount :: <fixed-integer>);
  546.   enqueue(stream, make(<indentation>, kind: kind, amount: amount));
  547. end;
  548.  
  549. // <block-start> -- internal.
  550. //
  551. // Represents the start of some logical-block.
  552. //
  553. define class <block-start> (<section-start>)
  554.   //
  555.   // The <block-end> op that corresponds to this <block-start>.  #f until
  556.   // this block ends.
  557.   slot block-start-block-end :: false-or(<block-end>), init-value: #f;
  558.   //
  559.   // The per-line-prefix, if there is one.
  560.   slot block-start-prefix :: false-or(<byte-string>),
  561.     required-init-keyword: prefix:;
  562.   //
  563.   // The suffix, if there is one.
  564.   slot block-start-suffix :: false-or(<byte-string>),
  565.     required-init-keyword: suffix:;
  566. end;
  567.  
  568. // start-logical-block -- internal.
  569. //
  570. // Qeueu up the start of a logical block.  Also, add the prefix to the end
  571. // of the buffer.
  572. //
  573. define method start-logical-block (stream :: <pretty-stream>,
  574.                    prefix :: false-or(<byte-string>),
  575.                    per-line? :: <boolean>,
  576.                    suffix :: false-or(<byte-string>))
  577.   if (prefix)
  578.     append-raw-output(stream, prefix, 0, prefix.size);
  579.   end;
  580.   let pending-blocks = stream.pretty-stream-pending-blocks;
  581.   let start = enqueue(stream,
  582.               make(<block-start>,
  583.                prefix: per-line? & prefix,
  584.                suffix: suffix,
  585.                depth: pending-blocks.size));
  586.   stream.pretty-stream-pending-blocks := pair(start, pending-blocks);
  587.   start;
  588. end;
  589.  
  590. // <block-end> -- internal.
  591. //
  592. // Represents the end of a logical-block.
  593. // 
  594. define class <block-end> (<queued-op>)
  595.   //
  596.   // The suffix for the block this block-end is ending.
  597.   slot block-end-suffix :: false-or(<byte-string>),
  598.     required-init-keyword: suffix:;
  599. end;
  600.  
  601. // end-logical-block -- internal
  602. //
  603. // Queue up the end of a logical-block.  Also, append the suffix (found in
  604. // the corresponding block-start) to the buffer.
  605. //
  606. define method end-logical-block (stream :: <pretty-stream>,
  607.                  aborted? :: <boolean>)
  608.   let blocks = stream.pretty-stream-pending-blocks;
  609.   let start = blocks.head;
  610.   stream.pretty-stream-pending-blocks := blocks.tail;
  611.   let suffix = ~aborted? & start.block-start-suffix;
  612.   let stop = enqueue(stream, make(<block-end>, suffix: suffix));
  613.   if (suffix)
  614.     append-raw-output(stream, suffix, 0, suffix.size);
  615.   end;
  616.   start.block-start-block-end := stop;
  617.   stop;
  618. end;
  619.  
  620. // <tab> -- internal.
  621. // 
  622. define class <tab> (<queued-op>)
  623.   //
  624.   // Various parameters for the tab.
  625.   slot tab-section? :: <boolean>, required-init-keyword: section?:;
  626.   slot tab-relative? :: <boolean>, required-init-keyword: relative?:;
  627.   slot tab-colnum :: <column>, required-init-keyword: colnum:;
  628.   slot tab-colinc :: <fixed-integer>, required-init-keyword: colinc:;
  629. end;
  630.  
  631. // enqueue-tab -- internal.
  632. //
  633. // Queue up a tab.  Not too exciting.
  634. // 
  635. define method enqueue-tab (stream :: <pretty-stream>,
  636.                kind :: one-of(#"line", #"line-relative",
  637.                       #"section", #"section-relative"),
  638.                colnum :: <column>, colinc :: <fixed-integer>)
  639.   let (section?, relative?)
  640.     = select (kind)
  641.     #"line" => values(#f, #f);
  642.     #"line-relative" => values(#f, #t);
  643.     #"section" => values(#t, #f);
  644.     #"section-relative" => values(#t, #t);
  645.       end;
  646.   enqueue(stream, make(<tab>, section?: section?, relative?: relative?,
  647.                colnum: colnum, colinc: colinc));
  648. end;
  649.  
  650.  
  651. //// Tab support.
  652.  
  653. // compute-tab-size -- internal.
  654. //
  655. // Figure out the size (i.e. number of spaces) this tab will expand to
  656. // if started at the given column and section-start.
  657. // 
  658. define method compute-tab-size (tab :: <tab>, section-start :: <column>,
  659.                 column :: <column>)
  660.   let origin = if (tab.tab-section?) section-start else 0 end;
  661.   let colnum = tab.tab-colnum;
  662.   let colinc = tab.tab-colinc;
  663.   if (tab.tab-relative?)
  664.     unless (colinc <= 1)
  665.       let newposn = column + colnum;
  666.       let rem = remainder(newposn, colinc);
  667.       unless (zero?(rem))
  668.     colnum := colnum + colinc - rem;
  669.       end;
  670.     end;
  671.     colnum;
  672.   elseif (column <= colnum + origin)
  673.     colnum + origin - column;
  674.   else
  675.     colinc - remainder(column - origin, colinc);
  676.   end;
  677. end;
  678.  
  679. // index-column -- internal.
  680. //
  681. // Figure out what column corresponds to the given index by expanding any
  682. // tabs that get in the way.  We just scan down the queue looking for tabs
  683. // that need to be expanded, keeping track of what column we are at and where
  684. // the latest section started.  Actaully, column and section-start hold the
  685. // delta between raw indexes and the real column (i.e. spaces added by tabs)
  686. // instead of the real column directly.  So we have to add the index in
  687. // if we want the real column.  We do this because it makes the record keeping
  688. // a little easier.
  689. // 
  690. define method index-column (index :: <index>, stream :: <pretty-stream>)
  691.   let column = stream.pretty-stream-buffer-start-column;
  692.   let section-start
  693.     = stream.pretty-stream-blocks.head.logical-block-section-column;
  694.   let end-posn = index-posn(index, stream);
  695.   block (return)
  696.     for (op in stream.pretty-stream-queue)
  697.       if (op.op-posn >= end-posn)
  698.     return();
  699.       end;
  700.       if (instance?(op, <tab>))
  701.     column := column
  702.       + compute-tab-size(op, section-start,
  703.                  column + posn-index(op.op-posn, stream));
  704.       elseif (instance?(op, <section-start>))
  705.     section-start := column + posn-index(op.op-posn, stream);
  706.       end;
  707.     end;
  708.   end;
  709.   column + index;
  710. end;
  711.  
  712. // expand-tabs -- internal.
  713. //
  714. // Find and expand (i.e. replace with spaces) the tabs up though the given
  715. // queued-op.  We do this in two passes.  First, we figure out how much
  716. // we need to insert where.  And second, we do the actual insertions.
  717. // 
  718. define method expand-tabs (stream :: <pretty-stream>,
  719.                through :: false-or(<queued-op>))
  720.   let insertions = #();
  721.   let additional = 0;
  722.   let column = stream.pretty-stream-buffer-start-column;
  723.   let section-start
  724.     = stream.pretty-stream-blocks.head.logical-block-section-column;
  725.   block (return)
  726.     for (op in stream.pretty-stream-queue)
  727.       if (instance?(op, <tab>))
  728.     let index = posn-index(op.op-posn, stream);
  729.     let tabsize = compute-tab-size(op, section-start, column + index);
  730.     unless (zero?(tabsize))
  731.       insertions := pair(pair(index, tabsize), insertions);
  732.       additional := additional + tabsize;
  733.       column := column + tabsize;
  734.     end;
  735.       elseif (instance?(op, <section-start>))
  736.     section-start := column + posn-index(op.op-posn, stream);
  737.       end;
  738.       if (op == through)
  739.     return();
  740.       end;
  741.     end;
  742.   end;
  743.   unless (empty?(insertions))
  744.     let fill-ptr = stream.pretty-stream-buffer-fill-pointer;
  745.     let new-fill-ptr = fill-ptr + additional;
  746.     let buffer = stream.pretty-stream-buffer;
  747.     let new-buffer = buffer;
  748.     let len = buffer.size;
  749.     let stop = fill-ptr;
  750.     if (new-fill-ptr > len)
  751.       let new-len = max(len * 2, fill-ptr + floor/(additional * 5, 4));
  752.       new-buffer := make(<byte-string>, size: new-len);
  753.       stream.pretty-stream-buffer := new-buffer;
  754.     end;
  755.     stream.pretty-stream-buffer-fill-pointer := new-fill-ptr;
  756.     stream.pretty-stream-buffer-offset
  757.       := stream.pretty-stream-buffer-offset - additional;
  758.     for (insertion in insertions)
  759.       let srcpos = insertion.head;
  760.       let amount = insertion.tail;
  761.       let dstpos = srcpos + additional;
  762.       let tabpos = dstpos - amount;
  763.       copy-bytes(new-buffer, dstpos, buffer, srcpos, stop - srcpos);
  764.       fill!(new-buffer, ' ', start: tabpos, end: dstpos);
  765.       additional := additional - amount;
  766.       stop := tabpos;
  767.     end;
  768.     unless (new-buffer == buffer)
  769.       copy-bytes(new-buffer, 0, buffer, 0, stop);
  770.     end;
  771.   end;
  772. end;
  773.  
  774.  
  775. //// Stuff to do the actual outputting.
  776.  
  777. // assure-space-in-buffer -- internal.
  778. //
  779. // Make sure there is some space in the buffer, and return how much that is.
  780. // If there isn't any space in the buffer, first try to output some stuff
  781. // in order to make space.  If that doesn't work, then grow the buffer.
  782. // 
  783. define method assure-space-in-buffer (stream :: <pretty-stream>,
  784.                       want :: <fixed-integer>)
  785.     => available :: <fixed-integer>;
  786.   let buffer = stream.pretty-stream-buffer;
  787.   let length = buffer.size;
  788.   let fill-ptr = stream.pretty-stream-buffer-fill-pointer;
  789.   let available = length - fill-ptr;
  790.   if (available > 0)
  791.     available;
  792.   elseif (fill-ptr > stream.pretty-stream-line-length)
  793.     unless (maybe-output(stream, #f))
  794.       output-partial-line(stream);
  795.     end;
  796.     assure-space-in-buffer(stream, want);
  797.   else
  798.     let new-length = max(length * 2, length + floor/(want * 5, 4));
  799.     let new-buffer = make(<byte-string>, size: new-length);
  800.     stream.pretty-stream-buffer := new-buffer;
  801.     copy-bytes(new-buffer, 0, buffer, 0, fill-ptr);
  802.     new-length - fill-ptr;
  803.   end;
  804. end;
  805.  
  806. // maybe-output -- internal.
  807. //
  808. // See if anything can be output, and if so, do so.  
  809. //
  810. // We scan down the queue, checking each op to see if there is anything
  811. // we can do.  If there isn't, then we leave that op in the queue and quit
  812. // the loop.
  813. //
  814. define method maybe-output (stream :: <pretty-stream>,
  815.                 force-newlines? :: <boolean>)
  816.     => did-anything? :: <boolean>;
  817.   let queue = stream.pretty-stream-queue;
  818.   let output-anything? = #f;
  819.   block (return)
  820.     for (until queue.empty?)
  821.       // Don't actually pop the queue until we've actually processed this op.
  822.       let next = queue.first;
  823.       if (instance?(next, <newline>))
  824.     // For newlines, check to see if we should break.  If so, output a
  825.     // line.
  826.     let kind = next.newline-kind;
  827.     if (if (kind == #"literal" | kind == #"mandatory" | kind == #"linear")
  828.           // We always break at linear newlines, because if this block
  829.           // fit on a single line, everything inside it would have been
  830.           // deleted from the queue.
  831.           #t;
  832.         elseif (kind == #"miser")
  833.           stream.misering?;
  834.         elseif (kind == #"fill")
  835.           stream.misering?
  836.         | (stream.pretty-stream-line-number
  837.              > (stream.pretty-stream-blocks.head
  838.               .logical-block-section-start-line))
  839.         | (select (fits-on-line?(stream,
  840.                      next.section-start-section-end,
  841.                      force-newlines?))
  842.              #t => #f;
  843.              #f => #t;
  844.              #"dont-know" => return();
  845.            end);
  846.         else
  847.           error("Strange kind of newline: %=", kind);
  848.         end)
  849.       output-anything? := #t;
  850.       output-line(stream, next);
  851.     end;
  852.       elseif (instance?(next, <indentation>))
  853.     // For indentations, set the indent level unless we are misering.
  854.     unless (misering?(stream))
  855.       set-indentation(stream,
  856.               next.indentation-amount
  857.                 + select (next.indentation-kind)
  858.                 #"block" =>
  859.                   stream.pretty-stream-blocks.head
  860.                     .logical-block-start-column;
  861.                 #"current" =>
  862.                   posn-column(next.op-posn, stream);
  863.                 otherwise =>
  864.                   error("Strange kind of indentation: %=",
  865.                     next.indentation-kind);
  866.                   end);
  867.     end;
  868.       elseif (instance?(next, <block-start>))
  869.     // For block-starts, check to see if the whole block fits on a line.
  870.     select (fits-on-line?(stream, next.section-start-section-end,
  871.                   force-newlines?))
  872.       #t =>
  873.         // If so, delete everything up to the block-end.  We leave the
  874.         // block-end in the queue so that when we get to the pop below
  875.         // it has something to remove.
  876.         let stop = next.block-start-block-end;
  877.         expand-tabs(stream, stop);
  878.         for (until queue.first == stop)
  879.           pop(queue);
  880.         end;
  881.       #f =>
  882.         // If not, then really start the logical block.
  883.         really-start-logical-block(stream,
  884.                        posn-column(next.op-posn, stream),
  885.                        next.block-start-prefix,
  886.                        next.block-start-suffix);
  887.       #"dont-know" =>
  888.         // If we can't tell, give up for now.
  889.         return();
  890.     end;
  891.       elseif (instance?(next, <block-end>))
  892.     // Done with this block.
  893.     really-end-logical-block(stream);
  894.       elseif (instance?(next, <tab>))
  895.     // Expand out the tab.
  896.     expand-tabs(stream, next);
  897.       else
  898.     error("Strange thing in queue: %=", next);
  899.       end;
  900.       pop(queue);
  901.     end;
  902.   end;
  903.   output-anything?;
  904. end;
  905.  
  906. // misering? -- internal.
  907. //
  908. // Return #t if we should be misering, #f if not.
  909. // 
  910. define method misering? (stream :: <pretty-stream>)
  911.   if (*print-miser-width*)
  912.     let line-len = stream.pretty-stream-line-length;
  913.     let blocks = stream.pretty-stream-blocks;
  914.     let start-column = blocks.head.logical-block-start-column;
  915.     line-len - start-column <= *print-miser-width*;
  916.   end;
  917. end;
  918.  
  919. // fits-on-line? -- internal.
  920. //
  921. // Return #t if everything until until-op fits on a single line, #f if not,
  922. // and #"dont-know" if we can't tell.
  923. // 
  924. define method fits-on-line? (stream :: <pretty-stream>,
  925.                  until-op :: false-or(<queued-op>),
  926.                  force-newlines? :: <boolean>)
  927.     => fits :: one-of(#t, #f, #"dont-know");
  928.   let available = stream.pretty-stream-line-length;
  929.   //(when (and *print-lines*
  930.   //           (= *print-lines* (pretty-stream-line-number stream)))
  931.   //  (decf available 3) ; for the `` ..''
  932.   //  (decf available (logical-block-suffix-length
  933.   //               (car (pretty-stream-blocks stream)))))
  934.   if (until-op)
  935.     posn-column(until-op.op-posn, stream) <= available;
  936.   elseif (force-newlines?)
  937.     #f;
  938.   elseif (index-column(stream.pretty-stream-buffer-fill-pointer, stream)
  939.         > available)
  940.     #f;
  941.   else
  942.     #"dont-know";
  943.   end;
  944. end;
  945.  
  946. // output-line -- internal.
  947. //
  948. // Actually output a line worth of stuff.  Newline is the newline that ends
  949. // this line.  All tabs will already have been expanded, so we don't have to
  950. // mess with them.
  951. // 
  952. define method output-line (stream :: <pretty-stream>, newline :: <newline>)
  953.   let target = stream.pretty-stream-target;
  954.   let buffer = stream.pretty-stream-buffer;
  955.   let kind = newline.newline-kind;
  956.   let literal? = kind == #"literal";
  957.   let amount-to-consume = posn-index(newline.op-posn, stream);
  958.   let amount-to-print
  959.     = if (literal?)
  960.     amount-to-consume;
  961.       else
  962.     // It it wasn't a literal newline, back up the amount we are going
  963.     // to print to get rid of any spaces at the end.
  964.     local method repeat (index)
  965.         if (zero?(index))
  966.           0;
  967.         else
  968.           let new-index = index - 1;
  969.           if (buffer[new-index] ~= ' ')
  970.             index;
  971.           else
  972.             repeat(new-index);
  973.           end;
  974.         end;
  975.           end;
  976.     repeat(amount-to-consume);
  977.       end;
  978.   write(buffer, target, start: 0, end: amount-to-print);
  979.   let line-number = stream.pretty-stream-line-number + 1;
  980.   //  (when (and *print-lines* (>= line-number *print-lines*))
  981.   //    (write-string " .." target)
  982.   //    (let ((suffix-length (logical-block-suffix-length
  983.   //                  (car (pretty-stream-blocks stream)))))
  984.   //      (unless (zerop suffix-length)
  985.   //        (let* ((suffix (pretty-stream-suffix stream))
  986.   //           (len (length suffix)))
  987.   //          (write-string suffix target
  988.   //                :start (- len suffix-length)
  989.   //                :end len))))
  990.   //    (throw 'line-limit-abbreviation-happened t))
  991.   write('\n', target);
  992.   stream.pretty-stream-line-number := line-number;
  993.   stream.pretty-stream-buffer-start-column := 0;
  994.   // Copy the per-line-prefix into the output buffer.  This also takes care of
  995.   // any indentation, as that has been added to the per-line-prefix buffer.
  996.   let fill-ptr = stream.pretty-stream-buffer-fill-pointer;
  997.   let next-block = stream.pretty-stream-blocks.head;
  998.   let prefix-len = if (literal?)
  999.              next-block.logical-block-per-line-prefix-end;
  1000.            else
  1001.              next-block.logical-block-prefix-length;
  1002.            end;
  1003.   let shift = amount-to-consume - prefix-len;
  1004.   let new-fill-ptr = fill-ptr - shift;
  1005.   let new-buffer = buffer;
  1006.   let buffer-length = buffer.size;
  1007.   if (new-fill-ptr > buffer-length)
  1008.     let extra = new-fill-ptr - buffer-length;
  1009.     new-buffer := make(<byte-string>,
  1010.                size: max(buffer-length * 2,
  1011.                  buffer-length + floor/(extra * 5, 4)));
  1012.     stream.pretty-stream-buffer := new-buffer;
  1013.   end;
  1014.   copy-bytes(new-buffer, prefix-len, buffer, amount-to-consume,
  1015.          fill-ptr - amount-to-consume);
  1016.   copy-bytes(new-buffer, 0, stream.pretty-stream-prefix, 0, prefix-len);
  1017.   stream.pretty-stream-buffer-fill-pointer := new-fill-ptr;
  1018.   stream.pretty-stream-buffer-offset
  1019.     := stream.pretty-stream-buffer-offset + shift;
  1020.   unless (literal?)
  1021.     next-block.logical-block-section-column := prefix-len;
  1022.     next-block.logical-block-section-start-line := line-number;
  1023.   end;
  1024. end;
  1025.  
  1026. // output-partial-line -- internal.
  1027. //
  1028. // Output as much of a line as we can.  Basically, everything up until the
  1029. // first op in the queue.
  1030. // 
  1031. define method output-partial-line (stream :: <pretty-stream>)
  1032.   let fill-ptr = stream.pretty-stream-buffer-fill-pointer;
  1033.   let queue = stream.pretty-stream-queue;
  1034.   let count = if (empty?(queue))
  1035.         fill-ptr;
  1036.           else
  1037.         posn-index(queue.first.op-posn, stream);
  1038.           end;
  1039.   let new-fill-ptr = fill-ptr - count;
  1040.   let buffer = stream.pretty-stream-buffer;
  1041.   if (zero?(count))
  1042.     error("Output-partial-line called when nothing can be output.");
  1043.   end;
  1044.   write(buffer, stream.pretty-stream-target, start: 0, end: count);
  1045.   stream.pretty-stream-buffer-start-column
  1046.     := stream.pretty-stream-buffer-start-column + count;
  1047.   copy-bytes(buffer, 0, buffer, count, new-fill-ptr);
  1048.   stream.pretty-stream-buffer-fill-pointer := new-fill-ptr;
  1049.   stream.pretty-stream-buffer-offset
  1050.     := stream.pretty-stream-buffer-offset + count;
  1051. end;
  1052.  
  1053.  
  1054. //// Interface routines.
  1055.  
  1056. // pprint-logical-block -- exported.
  1057. //
  1058. // Start a logical block, creating a pretty-stream if necessary.
  1059. // 
  1060. define generic pprint-logical-block
  1061.     (stream :: <stream>,
  1062.      #key column :: <integer>,
  1063.           prefix :: false-or(<byte-string>),
  1064.           per-line-prefix :: false-or(<byte-string>),
  1065.           body :: <function>,
  1066.           suffix :: false-or(<byte-string>))
  1067.     => ();
  1068.  
  1069. //
  1070. // When called on a regular stream, create <pretty-stream> and use it instead.
  1071. // 
  1072. define method pprint-logical-block
  1073.     (stream :: <stream>,
  1074.      #key column :: <integer> = 0,
  1075.           prefix :: false-or(<byte-string>),
  1076.           per-line-prefix :: false-or(<byte-string>),
  1077.           body :: <function>,
  1078.           suffix :: false-or(<byte-string>))
  1079.     => ();
  1080.   if (prefix & per-line-prefix)
  1081.     error("Can't specify both a prefix: and a per-line-prefix:");
  1082.   end;
  1083.   let stream = make(<pretty-stream>, target: stream, column: column);
  1084.   pprint-logical-block(stream,
  1085.                prefix: prefix,
  1086.                per-line-prefix: per-line-prefix,
  1087.                body: body,
  1088.                suffix: suffix);
  1089.   close(stream);
  1090. end;
  1091. //
  1092. // When called on a <pretty-stream>, just use it directly.
  1093. // 
  1094. define method pprint-logical-block
  1095.     (stream :: <pretty-stream>,
  1096.      #key column :: <integer> = 0,
  1097.           prefix :: false-or(<byte-string>),
  1098.           per-line-prefix :: false-or(<byte-string>),
  1099.           body :: <function>,
  1100.           suffix :: false-or(<byte-string>))
  1101.     => ();
  1102.   if (prefix & per-line-prefix)
  1103.     error("Can't specify both a prefix: and a per-line-prefix:");
  1104.   end;
  1105.   if (stream.pretty-stream-closed?)
  1106.     error("%= has been closed");
  1107.   end;
  1108.   let aborted? = #t;
  1109.   block ()
  1110.     start-logical-block(stream, prefix | per-line-prefix,
  1111.             per-line-prefix ~= #f,
  1112.             suffix);
  1113.     body(stream);
  1114.     aborted? := #f;
  1115.   cleanup
  1116.     end-logical-block(stream, aborted?);
  1117.   end;
  1118. end;
  1119.  
  1120. // pprint-newline -- exported.
  1121. //
  1122. // Output a conditional newline of some kind.  If called on a regular stream,
  1123. // ignore it.
  1124. // 
  1125. define generic pprint-newline (kind :: one-of(#"linear", #"miser", #"fill",
  1126.                           #"mandatory"),
  1127.                    stream :: <stream>)
  1128.     => ();
  1129. //
  1130. define method pprint-newline (kind :: one-of(#"linear", #"miser", #"fill",
  1131.                          #"mandatory"),
  1132.                   stream :: <stream>)
  1133.     => ();
  1134. end;
  1135. //
  1136. define method pprint-newline (kind :: one-of(#"linear", #"miser", #"fill",
  1137.                          #"mandatory"),
  1138.                   stream :: <pretty-stream>)
  1139.     => ();
  1140.   if (stream.pretty-stream-closed?)
  1141.     error("%= has been closed");
  1142.   end;
  1143.   enqueue-newline(stream, kind);
  1144. end;
  1145.  
  1146. // pprint-indent -- exported.
  1147. //
  1148. // Change the indentation.  If called on a regular stream, just ignore it.
  1149. // 
  1150. define generic pprint-indent (relative-to :: one-of(#"block", #"current"),
  1151.                   n :: <fixed-integer>, stream :: <stream>)
  1152.     => ();
  1153. //
  1154. define method pprint-indent (relative-to :: one-of(#"block", #"current"),
  1155.                  n :: <fixed-integer>, stream :: <stream>)
  1156.     => ();
  1157. end;
  1158. //
  1159. define method pprint-indent (relative-to :: one-of(#"block", #"current"),
  1160.                  n :: <fixed-integer>,
  1161.                  stream :: <pretty-stream>)
  1162.     => ();
  1163.   if (stream.pretty-stream-closed?)
  1164.     error("%= has been closed");
  1165.   end;
  1166.   enqueue-indent(stream, relative-to, n);
  1167. end;
  1168.  
  1169. // pprint-tab -- exported.
  1170. //
  1171. // Output a tab.  If called on a regular stream, just ignore it.
  1172. // 
  1173. define generic pprint-tab (kind :: one-of(#"line", #"line-relative",
  1174.                       #"section", #"section-relative"),
  1175.                colnum :: <fixed-integer>, colinc :: <fixed-integer>,
  1176.                stream :: <stream>)
  1177.     => ();
  1178. //
  1179. define method pprint-tab (kind :: one-of(#"line", #"section", #"line-relative",
  1180.                      #"section-relative"),
  1181.               colnum :: <fixed-integer>, colinc :: <fixed-integer>,
  1182.               stream :: <stream>)
  1183.     => ();
  1184. end;
  1185. //
  1186. define method pprint-tab (kind :: one-of(#"line", #"section", #"line-relative",
  1187.                      #"section-relative"),
  1188.               colnum :: <fixed-integer>, colinc :: <fixed-integer>,
  1189.               stream :: <pretty-stream>)
  1190.     => ();
  1191.   if (stream.pretty-stream-closed?)
  1192.     error("%= has been closed");
  1193.   end;
  1194.   enqueue-tab(stream, kind, colnum, colinc);
  1195. end;
  1196.  
  1197.